//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include <ddk.h>
#include <vkey.h>
#include <intcbits.h>
#include <bulverde.h>
#include <drvpub.h>
#include "keypad.h"

extern "C" unsigned char CheckKeyHold();

#if (_TEST_TYPE == 41)  /* Test for Runtime of ISR */
    #define  SAMPLETIMES       50
    static   ULONG     ulKeypadIsrTime[SAMPLETIMES];
    static   INT       nSampleCounter = 0;
#endif //_TEST_TYPE == 41

#if (_TEST_TYPE == 42)  /* Test for times of interrupt */
    static ULONG ulCounterOfKeypadIsr = 0;
    static ULONG ulCounterOfKeypadTimerIsr = 0;
    static ULONG ulCounterOfHangUpKeyIsr = 0;
#endif //_TEST_TYPE == 42


//#define _DBGKEYPAD
//#define _BDOOR_

#if defined(_BDOOR_)
EXTERN_C void __cdecl PosternEntry();
EXTERN_C void __cdecl PosternNotify();
EXTERN_C void __cdecl GPosternEntry();

#define RESET      0x0000
#define ENTRY      0x0001

#define IST_SETP1  0x10A1

#define IST_CONS2  0x10A2
#define IST_CONS3  0x10A3

#define IST_GDI2   0x30C2
#define IST_GDI3   0x30C3

#define ISR_SETP1  0x20B1
#define ISR_SETP2  0x20B2
#define ISR_SETP3  0x20B3


#define START_IST  0xF006
#define START_ISR  0xF007
#define START_GDI  0xF008

static long DoorSetp = 0;
static ULONG tmstmp = 0;
#endif

//
// OSSR bits are all sticky (write one to clear)
// Optimize macro with assignment, instead of ANDing)
//
#define TIMER_M0_INT_CLR(pOSSR) (pOSSR = OSSR_M0)
#define TIMER_M1_INT_CLR(pOSSR) (pOSSR = OSSR_M1)
#define TIMER_M2_INT_CLR(pOSSR) (pOSSR = OSSR_M2)
#define TIMER_M3_INT_CLR(pOSSR) (pOSSR = OSSR_M3)

#define TIMER_M0_INT_EN(pOIER)  (pOIER |= OIER_E0)
#define TIMER_M0_INT_DIS(pOIER) (pOIER &= ~OIER_E0)

#define TIMER_M1_INT_EN(pOIER)  (pOIER |= OIER_E1)
#define TIMER_M1_INT_DIS(pOIER) (pOIER &= ~OIER_E1)

#define TIMER_M2_INT_EN(pOIER)  (pOIER |= OIER_E2)
#define TIMER_M2_INT_DIS(pOIER) (pOIER &= ~OIER_E2)

#define TIMER_M3_INT_EN(pOIER)  (pOIER |= OIER_E3)
#define TIMER_M3_INT_DIS(pOIER) (pOIER &= ~OIER_E3)

#define XLLP_GPIO_BIT_RDY                   ( XLLP_BIT_18 )
extern "C" int KeypadInit();

ECode CKeypad::Read(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ UINT uNumberOfBytesToRead,
        /* [out] */ EzByteBuf ebbData,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    if (ebbData.IsNull() || ebbData.GetCapacity() < (int)sizeof(KPD_DATA)) {
        return E_INVALID_ARGUMENT;
    }

    return g_pEventQueues->Read(EVENT_KEYPAD,
        uNumberOfBytesToRead, ebbData, ppCompletionEvent);
}

ECode  CKeypad::Write(
        /* [in] */ UINT64 u64Offset,
        /* [in] */ EzByteBuf ebbData,
        /* [out] */ UINT * puNumberOfBytesWritten,
        /* [out] */ IEvent * * ppCompletionEvent)
{
    return E_NOT_IMPLEMENTED;
}

ECode CKeypad::Control(
        /* [in] */ INT nControlCode,
        /* [in] */ EzByteBuf ebbInData,
        /* [out] */ EzByteBuf * pOutData,
        /* [out] */ IEvent * * ppCompletionEvent)
{
   switch(nControlCode) {
        case 5: {
//            if (GPLR2 & (1 << 29)) {//Check OnKey Hold
//                break;
//            }
            unsigned char Key = CheckKeyHold();
            if (Key == 0xff) return E_TIMED_OUT;
            break;
        }
        case 1000:
            GPCR0 |= XLLP_GPIO_BIT_RDY;//turn off keypad backlight
            break;
        case 1001:
            GPSR0 |= XLLP_GPIO_BIT_RDY;//turn on keypad backlight
            break;
        case 1002:
            GPSR0 |= XLLP_GPIO_BIT_RDY;//turn on keypad backlight
            KeypadInit();
            break;


#if (_TEST_TYPE == 41)
        case 4100:
			memcpy(pOutData->GetPayload(),
					ulKeypadIsrTime,
					SAMPLETIMES * sizeof(ULONG));
		    pOutData->SetUsed(SAMPLETIMES * sizeof(ULONG));
            break;
#endif //_TEST_TYPE == 41
#if (_TEST_TYPE == 42)
        case 4200:
            *(ULONG *)(char *)ebbOutData = ulCounterOfKeypadIsr + \
                                           ulCounterOfKeypadTimerIsr + \
                                          ulCounterOfHangUpKeyIsr;
            pOutData->SetUsed(sizeof(ULONG));
            break;
#endif //_TEST_TYPE == 42


    }
    return NOERROR;
}

void CKeypad::Dispose()
{

}

/* A keyboard report is a state-change event.
 * It indicates what key was pressed/released
 * by the user, at the indicated time.
 */
typedef struct {
    /* Time at which sample was received,
     * or artificially generated.
     */
    TIME	ReportTime;

    /* Keyboard scan code.
     * Uniquely identifies the key, real
     * or presumed (overruns).
     */
    UINT16	MakeCode;

    /* Event type, one of up/down/special
     */
    UINT16	Flags;
} KEYBOARD_INPUT_DATA, *PKEYBOARD_INPUT_DATA;

extern "C" unsigned char KeypadRead();
extern "C" int CheckKeypadInterrupt();
/****************************************************************************
//Access virkey for TF smartphone
VK_F1               0x70    // F1 key
VK_F2               0x71    // F2 key
VK_F3               0x72    // F3 key
VK_F4               0x73    // F4 key
VK_F5               0x74    // F5 key
VK_VOLUME_MUTE         0xAD    // Volume Mute key
VK_VOLUME_DOWN         0xAE    // Volume Down key
VK_VOLUME_UP           0xAF    // Volume Up key
VK_MEDIA_NEXT_TRACK    0xB0    // Next Track key
VK_MEDIA_PREV_TRACK    0xB1    // Previous Track key
VK_MEDIA_STOP          0xB2    // Stop Media key
VK_MEDIA_PLAY_PAUSE    0xB3    // Play/Pause Media key
VK_PHONE_CALL          0x88    // call
VK_PHONE_HANG_UP       0x89    // hang up
VK_PHONE_SELECT_LEFT   0x8A    // select left
VK_PHONE_SELECT_RIGHT  0x8B    // select right
VK_CAMERA              0x8C
VK_RECORDER            0x8D
VK_HOME                0x8E

//lstatus
//                               Extended Key Status
//                               |
//         31|30|29|28|27|26|25|24|23|..|16|15|..|00
//          |
//          |
//          |
//          PressOrRelease
//
*****************************************************************************
*/
const unsigned char KeyCode[][2] = {
//ScanCode-->VKey
    {0x01, VK_VOLUME_DOWN},
    {0x02, VK_UP},
    {0x04, VK_PHONE_CALL},

    {0x11, VK_VOLUME_UP},
    {0x12, VK_RIGHT},
    {0x14, VK_DOWN},

    {0x21, VK_CAMERA},
    {0x42, VK_SELECT},
    {0x44, VK_LEFT},
};

EXTERN_C ULONG save_flags_cli();
EXTERN_C void restore_flags(ULONG);
BOOL PenTest(void);

DzEvent s_KeyEvt;
static unsigned char PreKey = 0xff;
//static UINT KeyStamp = 0;
static int KeyState = 0;
void CheckCpuIdle();


#if defined(_BDOOR_)
static long BDS_Machine(long curStat, ULONG Key)
{
    if (VK_RECORDER == Key) {
        return curStat;
    }
    long stat = RESET;
    switch (curStat) {
        case ENTRY:
            if (VK_VOLUME_UP == Key) {
                stat = IST_SETP1;
            }
            else if (VK_VOLUME_DOWN == Key) {
                stat = ISR_SETP1;
            }
            break;

        case IST_SETP1:
            if (VK_VOLUME_UP == Key) {
                stat = IST_CONS2;
            } else if(VK_VOLUME_DOWN == Key) {
                stat = IST_GDI2;
            }
            break;

//        case IST_SETP2:
//            if (VK_VOLUME_UP == Key) {
//                stat = IST_SETP3;
//            }
//            break;

        case IST_CONS2:
            if (VK_VOLUME_UP == Key) {
                stat = START_IST;
            }
            break;

        case IST_GDI2:
            if (VK_VOLUME_DOWN == Key) {
                stat = START_GDI;
            }
            break;

//        case IST_GDI3:
//            break;

        case ISR_SETP1:
            if (VK_VOLUME_DOWN == Key) {
                stat = ISR_SETP2;
            }
            break;

//        case ISR_SETP2:
//            if (VK_VOLUME_DOWN == Key) {
//                stat = ISR_SETP3;
//            }
//            break;

        case ISR_SETP2:
            if (VK_VOLUME_DOWN == Key) {
                stat = START_ISR;
            }
            break;

        default:
            break;
    }
    return stat;
}

static long BD_START(long CurStat)
{
    ULONG flags;
    long stat = CurStat;
    if (OSCR > (tmstmp + 3250 * 300 * 10) ) {
        stat = RESET;
    }
    switch(stat) {
        case START_IST:
            PosternNotify();//dummy
            stat = RESET;
            break;

        case START_ISR:
            flags = save_flags_cli();
            PosternEntry();//dummy
            restore_flags(flags);
            stat = RESET;
            break;

        case START_GDI:
            GPosternEntry();
            stat = RESET;
            break;

        default:
            stat = ENTRY;
    }
    return stat;
}

#endif

void CDECL KeypadIsr( irq_t irq, void *pvDevice, InterruptContext *pContext)
{
#if (_TEST_TYPE == 41)
    ULONG uTimerStart = OSCR0;
#endif //_TEST_TYPE == 41

#if defined(_DBGKEYPAD)
    BTTHR = 'i';
#endif

    if (0 == CheckKeypadInterrupt()) {
        s_KeyEvt.NotifyByIsr();
    }

#if (_TEST_TYPE == 41)
    ULONG uTimerEnd = OSCR0;
    if (nSampleCounter >= SAMPLETIMES) nSampleCounter = 0;
	ulKeypadIsrTime[nSampleCounter++] = \
	                    (uTimerEnd >= uTimerStart) ? \
	                    (uTimerEnd - uTimerStart) : \
	                    ((ULONG)0xFFFFFFFF - uTimerStart + uTimerEnd);
#endif //_TEST_TYPE == 41

#if (_TEST_TYPE == 42)
    ulCounterOfKeypadIsr++;
#endif //_TEST_TYPE == 42

    return ;
}

void CDECL KeyTimerIsr( irq_t irq, void *pvDevice, InterruptContext *pContext)
{
    //BTTHR = 'C';
    if ((KeyState == 1000) && (PreKey != 0xff)) {//check if normal key down
        BTTHR = 'P';
        KPD_DATA inputdata;
        inputdata.keycode = PreKey;
        inputdata.status = KEYDOWN;
        inputdata.status |= (1UL << 0);//T_CTRL
#if defined(_DBGKEYPAD)
kprintf("VKey %x %s\n", inputdata.keycode, (inputdata.status)?"Down":"Up");
#endif

#if defined(_BDOOR_)
        if (VK_RECORDER == PreKey) {
            DoorSetp = BD_START(DoorSetp);
            tmstmp = OSCR;
        }
#endif

        InputNotifyByIsr(EVENT_KEYPAD, 0, &inputdata);
    }
    else if ((KeyState > 0) && (GPLR2 & (1 << 29))) {//check if handup key always down

        if (KeyState < 5) {//try again
            KeyState ++;
            TIMER_M2_INT_CLR(OSSR);
            OSMR2 = OSCR + 3250*300;
            return ;
        }
        else {
            BTTHR = 'P';
            KPD_DATA inputdata;
            inputdata.keycode = VK_Power;
            inputdata.status = KEYDOWN;
#if defined(_DBGKEYPAD)
    kprintf("VKey %x %s\n", inputdata.keycode, (inputdata.status)?"Down":"Up");
#endif
            InputNotifyByIsr(EVENT_KEYPAD, 0, &inputdata);
        }
    }

    //Clear and disable the interrupt
    TIMER_M2_INT_CLR(OSSR);
    TIMER_M2_INT_DIS(OIER);

    KeyState = 0;//reset


#if (_TEST_TYPE == 42)
    ulCounterOfKeypadTimerIsr++;
#endif //_TEST_TYPE == 42
    return ;
}

ECode CKeypad::Initialize()
{
#if (_TEST_TYPE == 41)
	memset(ulKeypadIsrTime, 0, sizeof(ULONG) * SAMPLETIMES);
#endif //_TEST_TYPE == 41


    DzRegisterIsr(28/*OST_2*/, IPL5, (isr_t)KeyTimerIsr, (void*)this);

    DzRegisterIsr(4, IPL5, (isr_t)KeypadIsr, (void*)this);

    return NOERROR;
}

// Special Key: HangUp
void CDECL HangUpKeyIsr(void *pvDevice, BOOL *bArg)
{
    KPD_DATA inputdata;

    inputdata.keycode = VK_PHONE_HANG_UP;
    if (GPLR2 & (1 << 29)) {
#if 1
        BTTHR = 'K';
#endif
        // Key Down
        inputdata.status = KEYDOWN;
    }
    else {
        // Key Up
        inputdata.status = KEYUP;
    }
    GEDR2 |= (1 << 29);           // clear GPIO status

#if defined(_DBGKEYPAD)
    kprintf("Key %x %s\n", inputdata.keycode, (inputdata.status)?"Down":"Up");
#endif
    InputNotifyByIsr(EVENT_KEYPAD, 0, &inputdata);

    //start handup key timer
    if (inputdata.status == KEYDOWN) {

        //Clear and enable pen timer interrupt
        TIMER_M2_INT_CLR(OSSR);
        OSMR2 = OSCR + 3250*300;//push for shutdown
        //
        // Enable interrupts on Match 1
        //
        TIMER_M2_INT_EN(OIER);

        KeyState = 1;//handup key down state

    }
    else {
        KeyState = 0;//reset key state immediatly

        //Disable and clear the interrupt
        TIMER_M2_INT_DIS(OIER);
        TIMER_M2_INT_CLR(OSSR);
    }



#if (_TEST_TYPE == 42)
    ulCounterOfHangUpKeyIsr++;
#endif //_TEST_TYPE == 42
}

ECode KeyScanProc(void *arg)
{
    WaitResult wr;
    while (1) {
        //kprintf("+++++++Wait\n");
        s_KeyEvt.Wait(&wr, NULL);

        //CKeypad *pThis = (CKeypad *)pvDevice;
        unsigned char Key = KeypadRead();
        int i = 0;

        KPD_DATA inputdata;
        if (Key == 0xff) {//maybe key up
            if (PreKey != 0xff) {
                inputdata.status = KEYUP;
                inputdata.keycode = PreKey;
            }
        }
        else {//key down
            for(i = 0; i < (int)sizeof(KeyCode)/2; i++) {
                if (Key == KeyCode[i][0]) {
                    break;
                }
            }

            if (i == sizeof(KeyCode)/2) {//Unknown key
                PreKey = 0xff;
                continue ;
            }
            else Key = KeyCode[i][1];

            inputdata.status = KEYDOWN;
            inputdata.keycode = Key;
            {
                BTTHR = 'K';
    #if 0
                if (Key == VK_VOLUME_UP) {
                    BTTHR = 'V';
                    CheckCpuIdle();
                }
    #endif
            }
        }

        PreKey = Key;

    #if defined(_DBGKEYPAD)
        kprintf("Key %x %s\n", inputdata.keycode, (inputdata.status)?"Down":"Up");
    #endif
        /* notify to system notify center */
        InputNotifyByIsr(EVENT_KEYPAD, 0, &inputdata);

        //start key down timer
        if (inputdata.status == KEYDOWN) {
    #if defined(_BDOOR_)
            DoorSetp = BDS_Machine(DoorSetp, Key);
            if (DoorSetp != RESET) {
                if (OSCR > (tmstmp + 3250 * 300 * 10) ) {
                    DoorSetp = RESET;
                }
                tmstmp = OSCR;
            }
    #endif
            //Clear and enable pen timer interrupt
            TIMER_M2_INT_CLR(OSSR);
            OSMR2 = OSCR + 3250*1000;//check push
            // Enable interrupts on Match 1
            TIMER_M2_INT_EN(OIER);
            KeyState = 1000;//normal key
        }
        else {//stop key timer

            KeyState = 0;//reset key state

            //Disable and clear the interrupt
            TIMER_M2_INT_DIS(OIER);
            TIMER_M2_INT_CLR(OSSR);
        }

    }

    return NOERROR ;
}

static CKeypad gKeypad;
EXTERN IDeviceDriver * CDECL CreateKeypad(uint_t uDeviceNo, void *pvParameter)
{
    KeypadInit();
    //return false;

    CKeypad * pThis = &gKeypad;
    pThis->Initialize();
    ECode ec = DzCreateKernelThread(KeyScanProc,
                (PVoid)pThis, CreateThreadFlag_System, ThreadPriorityRank_Normal, NULL);

    if (FAILED(ec)) {
        kprintf("CreateKernelThread error\n");
        return NULL;
    }

	ICMR |= (0x1<<4);     // irq = 4, enable interrupt

    // VK_PHONE_HANG_UP USES GPIO PIN93
    GPDR2 &= ~(1 << 29);
    GAFR2_U |= (1 << 26);
    DzDelay(100);
    GRER2 |= (1 << 29);
    GFER2 |= (1 << 29);
    Drv_GpioInstall(93, (void*)pThis, (void *) HangUpKeyIsr);

    kprintf("Add Keypad %08x OK!\n", pThis);

    return pThis;
}

unsigned long  PoGetLcdPowerMode();
void InputNotifyByIsr(int eType, int nCode, void *pParam)
{
    //Notity PowerManager
    unsigned long premode=PoGetLcdPowerMode();
    //kprintf("the pre mode is %d\n",premode);
    if(premode==6/*Invalid powermode*/) return;
    NotifyByIsr(EVENT_INPUT, eType==EVENT_TOUCH, pParam);
    if(premode==2/*Power_Suspend*/) return;//if lcd power off,ignore the input.
    NotifyByIsr(eType, nCode, pParam);
}
